﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Serialization;
using LibCom;

namespace RNMazeCom {

    class Game {
        
        // used Streams and Sockets
        private NetworkStream ns;
        private TcpClient client;
        private UTF8StringWriter sw;
        private StringReader sr;
        private XmlSerializer xmlSerializer;
        
        // information about the current board
        private boardType board;

        // server data
        private readonly string SERVER_IP = "127.0.0.1";
        private readonly int PORT = 5123;

        // information of player
        private string name;
        private int id;
        // constructor
        public Game() {

            // initialize variables
            client = new TcpClient();
            sw = new UTF8StringWriter();

            name = "RiMaDo-Team";
        }

        // connects to server at given IP-adress and port
        public void connectToServer() {

            client.Connect(SERVER_IP, PORT);
            ns = client.GetStream();
        }

        public int[] getPlayers(cardType[][] Board)
        {
            List<int> ret=new List<int>();
            foreach (var v in Board)
                foreach (var u in v)
                    foreach (var t in u.pin)
                        ret.Add(t);

            ret.Sort();
            while (ret[0] != id)
            {
                ret.Add(ret[0]);
                ret.Remove(ret[0]);
            }
            return (int[]) ret.ToArray();
        }

        // login into game as new player
        // todo: handle AcceptMessage and DisconnetMessage
        private bool login() {

            // create MazeCom object
            MazeCom loginMes = new MazeCom();
            loginMes.mcType = MazeComType.LOGIN;

            // create a loginMessage and set the team-name
            LoginMessageType l = new LoginMessageType();
            l.name = name;

            // set the type of the MazeCom object to the
            // just created loginMessage
            loginMes.Item = l;
            loginMes.id = 0;

            // create an XmlSerializer specified to the given xml-object-type
            xmlSerializer = new XmlSerializer(loginMes.GetType());
            // serialize the loginMessage into the stream
            xmlSerializer.Serialize(sw, loginMes);

            // send the message to the server
            writeUTF8(sw.ToString());

            // get login reply message fro the server
            string xmlString = readUTF8();

            // deserialize the received Message
            sr = new StringReader(xmlString);
            MazeCom loginReply = (MazeCom)xmlSerializer.Deserialize(sr);

            // get the LoginReplyMessage from the MazeCom object
            LoginReplyMessageType reply = (LoginReplyMessageType) loginReply.Item;
            // get the id, given by the server
            id = reply.newID;

            return true;
        }

        // wait for an AwaitMoveMessage request from the server
        // and answer with a MoveMessage
        public void run() {
            while (true) {
                // get AwaitMoveMessage
                string xmlString = readUTF8();

                //Console.WriteLine(xmlString);

                sr = new StringReader(xmlString);
                MazeCom request = (MazeCom)xmlSerializer.Deserialize(sr);

                AwaitMoveMessageType awaitMove = new AwaitMoveMessageType();
                if (request.mcType == MazeComType.AWAITMOVE) {

                    awaitMove = (AwaitMoveMessageType)request.Item;
                    board = awaitMove.board;
                }
                System.Diagnostics.Stopwatch stw = System.Diagnostics.Stopwatch.StartNew();
                List<boardTypeRow> list = board.row;
                cardType[][] boardRows=new cardType[7][];


                for(int i = 0; i < 7; i++)
                {
                    boardRows[i] = new cardType[7];
                    for (int k = 0; k < 7; k++)
                    {
                        boardRows[i][k] = list[i].col[k];
                    }
                }

                int[] einschub = new int[2];
                einschub[0] = board.forbidden!= null ? board.forbidden.row : -1;
                einschub[1] = board.forbidden!= null ?  board.forbidden.col : -1;

                Zug newMove = LibCom.Connecting.getZug(boardRows, board.shiftCard, (treasureType)awaitMove.treasure, einschub, getPlayers(boardRows));

                // create a new MazeCom object
                // and answer with the new move
                MoveMessageType newMoveMessage = new MoveMessageType();

                positionType playerPos = new positionType();
                playerPos.row = newMove.moveTo[0];
                playerPos.col = newMove.moveTo[1];
                newMoveMessage.newPinPos = playerPos;

                positionType insertPos = new positionType();
                insertPos.row = newMove.insert[0];
                insertPos.col = newMove.insert[1];
                newMoveMessage.shiftPosition = insertPos;
                
                newMoveMessage.shiftCard = newMove.CardToInsert;
                MazeCom answer = new MazeCom();
                answer.id = id;
                answer.mcType = MazeComType.MOVE;
                answer.Item = newMoveMessage;

                stw.Stop();
                Console.WriteLine("Speed {0}", stw.Elapsed);
                // serialize the newMoveMessage into the stream
                sw = new UTF8StringWriter();
                xmlSerializer = new XmlSerializer(answer.GetType());
                xmlSerializer.Serialize(sw, answer);

                Console.WriteLine(sw.ToString());

                // send the message to the server
                writeUTF8(sw.ToString());

                // get login reply message fro the server
                xmlString = readUTF8();

                // deserialize the received Message
                sr = new StringReader(xmlString);
                MazeCom acceptMessage = (MazeCom)xmlSerializer.Deserialize(sr);

                // get the LoginReplyMessage from the MazeCom object
                AcceptMessageType reply = (AcceptMessageType)acceptMessage.Item;

                Console.WriteLine("Accept: " + reply.accept);
                Console.WriteLine("Error Code: "+reply.errorCode);
            }
        }

        // to send the serialzed xml object to the server
        public void writeUTF8(String text) {

            byte[] bytes = sw.Encoding.GetBytes(text);
		    int len = bytes.Length;

		    byte[] lenbuf = new byte[4];
		    lenbuf[0] = (byte) (len & 0xff);
		    len >>= 8;
		    lenbuf[1] = (byte) (len & 0xff);
		    len >>= 8;
		    lenbuf[2] = (byte) (len & 0xff);
		    len >>= 8;
		    lenbuf[3] = (byte) (len & 0xff);

            ns.Write(lenbuf, 0, lenbuf.Length);
            ns.Write(bytes, 0, bytes.Length);
	    }

        // to read a serialzed xml object from the server
        public String readUTF8() {

            byte[] tmp = new byte[4];
            ns.Read(tmp,0,4);

		    int len = 0;
		    len |= (tmp[3] & 0xff);
		    len <<= 8;
		    len |= (tmp[2] & 0xff);
		    len <<= 8;
		    len |= (tmp[1] & 0xff);
		    len <<= 8;
		    len |= (tmp[0] & 0xff);

            byte[] bytes = new byte[len];
            ns.Read(bytes,0,len);

            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            String message = enc.GetString(bytes);
            
		    return message;
	    }

        public static void Main(string[] args) {

            Game game = new Game();

            // connect to Server
            game.connectToServer();

            // login into game as new player
            // and play the game
            if (game.login()) {

                game.run();
            }

            Console.ReadLine();
        }
    }
}
